﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using TestcraftLibrary.CVar;
using TestcraftLibrary.Common;
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Threading;

namespace Testcraft
{
    class ChunkEngine
    {
        public const int ChunkRadius = 30;
        SamplerState BlockSamplerState = new SamplerState() { Filter = TextureFilter.PointMipLinear, AddressU = TextureAddressMode.Wrap, AddressV = TextureAddressMode.Wrap, AddressW = TextureAddressMode.Wrap };
        GraphicsDevice device;
        private TestCraftGame gameInstance;
        private const float ChunkPartExpireTimeout = 900.0f;
        private BasicEffect occlusionEffect = null;
        private BlendState occlusionBlendState;
        public List<Chunk> loadedChunks;
        public ConcurrentQueue<Chunk> pendingChunks;
        public List<Chunk> loadingChunks;
        public List<OperationRequest<Point>> requestedChunks;
        private List<Chunk> frustumChunks;
        private ConcurrentQueue<Chunk> downloadedChunks;
        private ICamera camera;
        private Texture2D[] blockTextures;
        public bool onlyOne;
        private Thread updateThread;
        private AutoResetEvent updateEvent = new AutoResetEvent(false);
        private bool shutdown = false;



#if DEBUG_METRICS
        public int TotalCompressedChunks = 0;
        public int FrustumChunks = 0;
        public int SortedChunkParts = 0;
        public int VisibleChunkParts = 0;
#endif

        public Player Player { get; set; }

        public static int[,] BlockTextureLookup = new int[Enum.GetValues(typeof(BlockType)).Length, 6];

        public ChunkEngine(TestCraftGame gameInstance)
        {
            this.device = gameInstance.GraphicsDevice;
            this.gameInstance = gameInstance;
            //this.gameInstance.ServerConnection.MessageReceived += new EventHandler<TestcraftLibrary.Network.MessageReceivedEventArgs>(ServerConnection_MessageReceived);
            this.occlusionBlendState = new BlendState()
            {
                ColorWriteChannels = ColorWriteChannels.None,
                ColorWriteChannels1 = ColorWriteChannels.None,
                ColorWriteChannels2 = ColorWriteChannels.None,
                ColorWriteChannels3 = ColorWriteChannels.None
            };
            this.loadedChunks = new List<Chunk>();
            this.frustumChunks = new List<Chunk>();
            this.requestedChunks = new List<OperationRequest<Point>>();
            this.pendingChunks = new ConcurrentQueue<Chunk>();
            this.downloadedChunks = new ConcurrentQueue<Chunk>();
            this.loadingChunks = new List<Chunk>();
            this.LoadTextures();
        }

        void ServerConnection_MessageReceived(object sender, TestcraftLibrary.Network.MessageReceivedEventArgs e)
        {

        }

        public void RunUpdateCycle()
        {
            this.updateEvent.Set();
        }

        private void LoadTextures()
        {
            string textureFolder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create), "Testcraft\\textures");
            CVar cvar = gameInstance.CVarSystem.Get("skin_name");
            if (cvar == null)
            {
                cvar = new CVar("skin_name", "The name of the current skin used for texturing.", "default", true);
                gameInstance.CVarSystem.Set(cvar);
            }

            textureFolder = System.IO.Path.Combine(textureFolder, cvar.GetString());
            string[] textureFilePaths = System.IO.Directory.GetFiles(textureFolder, "*.png");
            string[] textureNames = textureFilePaths.Select(x => System.IO.Path.GetFileNameWithoutExtension(x)).ToArray();

            string[] blockNames = Enum.GetNames(typeof(BlockType));
            List<Texture2D> textures = new List<Texture2D>();

            Dictionary<string, Texture2D> loadedTextures = new Dictionary<string, Texture2D>();

            this.blockTextures = new Texture2D[blockNames.Length];

            for (ushort blockId = 0; blockId < blockNames.Length; blockId++)
            {
                // Check for "uniform" texture, meaning that the block as the same texture on all sides.
                int uniformTextureFileIndex = Array.IndexOf<string>(textureNames, blockNames[blockId]);
                if (uniformTextureFileIndex != -1)
                {
                    Texture2D texture;
                    if (!loadedTextures.TryGetValue(textureFilePaths[uniformTextureFileIndex], out texture))
                    {
                        System.IO.FileStream fileStream = null;
                        try
                        {
                            fileStream = new System.IO.FileStream(textureFilePaths[uniformTextureFileIndex], System.IO.FileMode.Open);
                            texture = Texture2D.FromStream(device, fileStream);
                            loadedTextures.Add(textureFilePaths[uniformTextureFileIndex], texture);
                        }
                        finally
                        {
                            if (fileStream != null)
                                fileStream.Close();
                        }
                    }

                    int textureIndex = textures.IndexOf(texture);
                    if (textureIndex == -1)
                    {
                        textureIndex = textures.Count;
                        textures.Add(texture);
                    }
                    for (int dir = 0; dir < 6; dir++)
                    {
                        ChunkEngine.BlockTextureLookup[blockId, dir] = textureIndex;
                    }
                }
                else
                {
                    int defaultFaceTextureFileIndex = Array.IndexOf<string>(textureNames, string.Format("{0}-face", blockNames[blockId]));
                    for (Direction dir = Direction.PositiveX; dir <= Direction.NegativeZ; dir++)
                    {
                        int faceTextureFileIndex = Array.IndexOf<string>(textureNames, string.Format("{0}-{1}", blockNames[blockId], dir.ToString()));
                        if (faceTextureFileIndex == -1)
                        {
                            if (defaultFaceTextureFileIndex == -1)
                                throw new EncoderFallbackException("MEHMEH");
                            else
                                faceTextureFileIndex = defaultFaceTextureFileIndex;
                        }


                        Texture2D texture;
                        if (!loadedTextures.TryGetValue(textureFilePaths[faceTextureFileIndex], out texture))
                        {
                            System.IO.FileStream fileStream = null;
                            try
                            {
                                fileStream = new System.IO.FileStream(textureFilePaths[faceTextureFileIndex], System.IO.FileMode.Open);
                                texture = Texture2D.FromStream(device, fileStream);
                                loadedTextures.Add(textureFilePaths[faceTextureFileIndex], texture);
                            }
                            finally
                            {
                                if (fileStream != null)
                                    fileStream.Close();
                            }
                        }

                        int textureIndex = textures.IndexOf(texture);
                        if (textureIndex == -1)
                        {
                            textureIndex = textures.Count;
                            textures.Add(texture);
                        }

                        ChunkEngine.BlockTextureLookup[blockId, (int)dir] = textureIndex;
                    }
                }
            }

            this.blockTextures = textures.ToArray();

        }

        private int ChunkPartSortComparer(ChunkPartEx2 a, ChunkPartEx2 b)
        {
            if (b == null && a != null)
                return 1;
            else if (b != null && a == null)
                return -1;
            else if (b == null && a == null)
                return 0;

            Vector3 pos_a = new Vector3((a.Parent.X * Chunk.ChunkWidth) + (Chunk.ChunkWidth / 2.0f), (a.Index * Chunk.ChunkHeight) + (Chunk.ChunkHeight / 2.0f), (a.Parent.Y * Chunk.ChunkWidth) + (Chunk.ChunkWidth / 2.0f));
            Vector3 pos_b = new Vector3((b.Parent.X * Chunk.ChunkWidth) + (Chunk.ChunkWidth / 2.0f), (b.Index * Chunk.ChunkHeight) + (Chunk.ChunkHeight / 2.0f), (b.Parent.Y * Chunk.ChunkWidth) + (Chunk.ChunkWidth / 2.0f));

            float distance_a = Vector3.DistanceSquared(this.camera.Position, pos_a);
            float distance_b = Vector3.DistanceSquared(this.camera.Position, pos_b);

            return distance_a.CompareTo(distance_b);
        }

        public Chunk GetChunk(int x, int y)
        {
            Chunk result = null;
            for (int i = 0; i < this.loadedChunks.Count; i++)
            {
                if (this.loadedChunks[i] != null && this.loadedChunks[i].X == x && this.loadedChunks[i].Y == y)
                {
                    result = this.loadedChunks[i];
                    break;
                }
            }

            return result;
        }

        public void DrawChunks(GameTime gameTime, GraphicsDevice device, BasicEffect effect, ICamera camera)
        {
            Stopwatch watch = Stopwatch.StartNew();
            effect.FogColor = Color.SkyBlue.ToVector3();

            effect.FogStart = (ChunkRadius / 2.0f - 3) * 16;
            effect.FogEnd = (ChunkRadius / 2.0f - 2) * 16;
            device.SamplerStates[0] = this.BlockSamplerState;

            if (this.occlusionEffect == null)
                this.occlusionEffect = new BasicEffect(device);
            this.camera = camera;
            BoundingFrustum frustum = camera.Frustum;
            BinarySortedList<ChunkPartEx2> sortedChunkParts = new BinarySortedList<ChunkPartEx2>(1000);
            List<Chunk> visibleChunks = new List<Chunk>(this.loadedChunks.Count);
            //List<Chunk> invisibleChunks = new List<Chunk>();

#if DEBUG_METRICS
            this.FrustumChunks = 0;
            this.VisibleChunkParts = 0;
#endif

            for (int i = 0; i < this.loadedChunks.Count; i++)
            {
                if (frustum.Intersects(this.loadedChunks[i].Bounds))
                {
#if DEBUG_METRICS
                    this.FrustumChunks++;
#endif
                    //visibleChunks.Add(this.loadedChunks[i]);
                    for (int j = 0; j < 16; j++)
                    {
                        if (this.loadedChunks[i].partsEx2[j] != null)
                        {
                            double key = (double)Vector3.DistanceSquared(this.camera.Position, new Vector3((this.loadedChunks[i].X * Chunk.ChunkWidth) + (Chunk.ChunkWidth / 2.0f), (j * Chunk.ChunkHeight) + (Chunk.ChunkHeight / 2.0f), (this.loadedChunks[i].Y * Chunk.ChunkWidth) + (Chunk.ChunkWidth / 2.0f)));

                            sortedChunkParts.Add(new KeyValuePair<double, ChunkPartEx2>(key, this.loadedChunks[i].partsEx2[j]));
                        }
                    }
                }

                //else
                //    invisibleChunks.Add(this.loadedChunks[i]);
            }
#if DEBUG_METRICS
            this.SortedChunkParts = sortedChunkParts.Count;
#endif

            //ChunkPartEx2[] visibleChunkParts = new ChunkPartEx2[16 * visibleChunks.Count];
            //int visibleChunkPartIndex = 0;
            //for (int i = 0; i < visibleChunks.Count; i++)
            //{
            //    for (int j = 0; j < 16; j++)
            //    {
            //        visibleChunkParts[visibleChunkPartIndex++] = visibleChunks[i].partsEx2[j];
            //    }
            //}

            //Stopwatch watch2 = Stopwatch.StartNew();
            //Array.Sort(visibleChunkParts, ChunkPartSortComparer);
            //watch2.Stop();
            //Console.WriteLine(visibleChunkParts.Length);

            for (int i = sortedChunkParts.list.Count - 1; i >= 0; i--)
            {
                if (sortedChunkParts.list[i].Value != null && !sortedChunkParts.list[i].Value.IsDisposed /*&& System.Threading.Interlocked.CompareExchange(ref visibleChunkParts[i].DisposeGuard, 0, 1) == 1*/)
                {

                    bool inFrustum = sortedChunkParts.list[i].Value.Bounds.Intersects(frustum);

                    bool queryCompleted = sortedChunkParts.list[i].Value.occlusionQuery.IsComplete;

                    if (!inFrustum)
                    {
                        sortedChunkParts.list[i].Value.isVisible = false;
                    }
                    else if (queryCompleted)
                    {
                        bool visible = (sortedChunkParts.list[i].Value.occlusionQuery.PixelCount > 0);

                        if (visible && !sortedChunkParts.list[i].Value.isVisible)
                        {
                            sortedChunkParts.list[i].Value.visibleQueryTimer = 0;
                            sortedChunkParts.list[i].Value.isVisible = true;

                        }
                        else if (!visible && sortedChunkParts.list[i].Value.isVisible)
                        {
                            sortedChunkParts.list[i].Value.isVisible = false;
                            sortedChunkParts.list[i].Value.expireTimer = 0;
                        }
                    }

                    if (sortedChunkParts.list[i].Value.isVisible)
                    {
#if DEBUG_METRICS
                        this.VisibleChunkParts++;
#endif
                        if (queryCompleted && sortedChunkParts.list[i].Value.visibleQueryTimer == 30)
                            sortedChunkParts.list[i].Value.occlusionQuery.Begin();
                        device.BlendState = BlendState.Opaque;
                        device.DepthStencilState = DepthStencilState.Default;

                        if (System.Threading.Monitor.TryEnter(sortedChunkParts.list[i].Value.buffers, 0))
                        {
                            foreach (BlockBuffer buffer in sortedChunkParts.list[i].Value.buffers.Values)
                            {
                                effect.Texture = this.blockTextures[buffer.id];
                                effect.CurrentTechnique.Passes[0].Apply();
                                device.SetVertexBuffer(buffer.vbuffer);
                                device.Indices = buffer.ibuffer;

                                device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, buffer.vbufferSize, 0, buffer.ibufferSize / 3);
                                device.SetVertexBuffer(null);
                                device.Indices = null;
                            }
                            System.Threading.Monitor.Exit(sortedChunkParts.list[i].Value.buffers);
                        }

                        if (queryCompleted && sortedChunkParts.list[i].Value.visibleQueryTimer >= 30)
                        {
                            sortedChunkParts.list[i].Value.occlusionQuery.End();
                            sortedChunkParts.list[i].Value.visibleQueryTimer = 0;
                        }
                        sortedChunkParts.list[i].Value.visibleQueryTimer++;
                    }
                    else
                    {
                        if (inFrustum)
                        {
                            if (queryCompleted || sortedChunkParts.list[i].Value.untouched)
                                sortedChunkParts.list[i].Value.occlusionQuery.Begin();

                            RasterizerState previousState = this.device.RasterizerState;
                            this.device.RasterizerState = RasterizerState.CullNone;
                            this.device.BlendState = this.occlusionBlendState;
                            device.DepthStencilState = DepthStencilState.DepthRead;
                            this.occlusionEffect.World = Matrix.Identity;
                            this.occlusionEffect.View = camera.View;
                            this.occlusionEffect.Projection = camera.Projection;
                            this.occlusionEffect.CurrentTechnique.Passes[0].Apply();


                            sortedChunkParts.list[i].Value.occlusionBlock.Draw();

                            if (queryCompleted || sortedChunkParts.list[i].Value.untouched)
                                sortedChunkParts.list[i].Value.occlusionQuery.End();

                            if (sortedChunkParts.list[i].Value.untouched)
                                sortedChunkParts.list[i].Value.untouched = false;

                            this.device.RasterizerState = previousState;
                        }

                    }

                    //visibleChunkParts[i].DisposeGuard = 1;
                }
                if (onlyOne)
                    return;
            }

            watch.Stop();
            //Console.WriteLine("D: {0}", watch.ElapsedMilliseconds);
        }

        public void Start()
        {
            this.updateThread = new System.Threading.Thread(this.UpdateLoop);
            this.updateThread.Name = "ChunkEngine:UpdateLoop";
            this.updateThread.Priority = System.Threading.ThreadPriority.Lowest;
            this.updateThread.IsBackground = true;
            this.updateThread.Start();
        }

        bool mustNotUpdate = false;
        public void WaitForEndOfChunkUpdate()
        {
            this.mustNotUpdate = true;
            this.updateEvent.Set();
            this.updateEvent.Reset();
            this.updateIteration.WaitOne();
            this.mustNotUpdate = false;
        }

        private List<Point> GetPointsInCircle(int originX, int originY, int radius)
        {
            List<Point> points = new List<Point>();
            for (int y = 0; y < radius; y++)
            {
                for (int x = 0; x < radius; x++)
                {
                    double length = Math.Sqrt(Math.Pow(x, 2.0) + Math.Pow(y, 2.0));
                    if (length <= radius)
                    {
                        points.Add(new Point(originX + x, originY + y));
                        points.Add(new Point(originX + -x, originY + y));
                        points.Add(new Point(originX + x, originY + -y));
                        points.Add(new Point(originX + -x, originY + -y));
                    }
                }
            }

            return points;
        }

        private List<Point> GetPlayerChunks()
        {
            int playerChunkX = (int)(this.Player.Position.X / Chunk.ChunkWidth);
            int playerChunkZ = (int)(this.Player.Position.Z / Chunk.ChunkWidth);

            return this.GetPointsInCircle(playerChunkX, playerChunkZ, ChunkRadius / 2);

            //List<Point> chunks = new List<Point>((int)Math.Pow(ChunkRadius + 1, 2) + 1);
            //for (int x = -(ChunkRadius / 2); x <= (int)(ChunkRadius - (ChunkRadius / 2.0f)); x++)
            //{
            //    for (int z = -(ChunkRadius / 2); z <= (int)(ChunkRadius - (ChunkRadius / 2.0f)); z++)
            //    {
            //        Point p = new Point(playerChunkX + x, playerChunkZ + z);
            //        chunks.Add(p);
            //    }
            //}

            //return chunks;
        }

        public void Shutdown()
        {
            this.shutdown = true;
            this.updateEvent.Set();
            this.updateThread.Join();
        }

        AutoResetEvent updateIteration = new AutoResetEvent(false);

        private void UpdateLoop()
        {
            //System.Threading.ThreadPool.SetMaxThreads(3, 3);
            while (!this.shutdown)
            {
                this.updateIteration.Set();
                this.updateEvent.WaitOne();
                this.updateIteration.Reset();
                if (!this.shutdown)
                {
                    /* Dispose old chunks */
                    OperationRequest<Chunk> disposeRequest;
                    while (this.disposeQueue.TryDequeue(out disposeRequest))
                    {
                        if (!this.shutdown)
                            disposeRequest.data.Dispose();
                    }

                    /* Load new chunks */
                    BinarySortedList<OperationRequest<Point>> chunkRequests = new BinarySortedList<OperationRequest<Point>>(32);
                    OperationRequest<Point> chunkRequest;
                    while (this.requestedChunkQueue.TryDequeue(out chunkRequest))
                    {
                        double num1 = chunkRequest.data.X * Chunk.ChunkWidth - this.Player.Position.X;
                        double num2 = chunkRequest.data.Y * Chunk.ChunkWidth - this.Player.Position.Z;
                        chunkRequests.Add(new KeyValuePair<double, OperationRequest<Point>>((num1 * num1) + (num2 * num2), chunkRequest));
                    }

                    for (int i = 0; i < chunkRequests.Count; i++)
                    {
                        if (!this.shutdown &&
                            chunkRequests.list[i].Value.SignalRunning())
                        {
                            this.RequestNewChunk(chunkRequests.list[i].Value.data);
                            chunkRequests.list[i].Value.SignalCompleted();
                        }
                    }

                    /* Compress distant chunks */
                    OperationRequest<Chunk> compressionRequest;
                    while (this.compressionQueue.TryDequeue(out compressionRequest))
                    {
                        if (!this.shutdown &&
                            compressionRequest.SignalRunning())
                        {
                            compressionRequest.data.Compress();
                            compressionRequest.data.compressionStatus = Chunk.CompressionStatus.Compressed;
                            compressionRequest.SignalCompleted();
                        }
                    }

                    /* Decompress chunks */
                    OperationRequest<Chunk> decompressionRequest;
                    while (this.decompressionQueue.TryDequeue(out decompressionRequest))
                    {
                        if (!this.shutdown &&
                            decompressionRequest.SignalRunning())
                        {
                            decompressionRequest.data.Decompress();
                            decompressionRequest.data.compressionStatus = Chunk.CompressionStatus.Decompressed;
                            decompressionRequest.SignalCompleted();
                            this.ChunkReceived(decompressionRequest.data);
                        }
                    }
                }
            }
        }

        private void RequestNewChunk(Point p)
        {
            //System.Threading.ThreadPool.UnsafeQueueUserWorkItem(
            // Skicka request till servern
            //System.Threading.Tasks.Task generateTask = new System.Threading.Tasks.Task(new Action<object>(this.GenerateChunk), (object)p);
            //generateTask.Start();
            //System.Threading.Tasks.Task.Factory.StartNew(this.GenerateChunk, (object)p, System.Threading.Tasks.TaskCreationOptions.AttachedToParent);
            GenerateChunk(p);
        }

        private void GenerateChunk(object p)
        {
            Point point = (Point)p;
            List<BlockDescriptorAdd> sureThing = new List<BlockDescriptorAdd>(16 * 16 * 250);

            Random randy = new Random((int)DateTime.Now.Ticks);
            for (short blockx = 0; blockx < 16; blockx++)
            {
                for (short blockz = 0; blockz < 16; blockz++)
                {
                    int height = 1;
                    if (blockx == blockz)
                        height = 1;
                    for (short blocky = 0; blocky < height; blocky++)
                    {
                        {

                            BlockDescriptorAdd descriptor;
                            if (blocky == height - 1)
                                descriptor.Type = (ushort)BlockType.Grass;
                            else
                                descriptor.Type = (ushort)Block.UninitializedBlockID;
                            //descriptor.Location.X = (short)(point.X + blockx);
                            descriptor.Location.X = (short)blockx;
                            //descriptor.Location.Y = (short)(height + ((((7.5f - Math.Abs((-7.5f + blockx))) / 7.5f) *
                            //    ((7.5f - Math.Abs((-7.5f + blockz))) / 7.5f)) * 10.0f));
                            descriptor.Location.Y = (short)blocky;
                            //descriptor.Location.Z = (short)(point.Y + blockz);
                            descriptor.Location.Z = (short)blockz;
                            sureThing.Add(descriptor);
                       } 
                    }
                }
            }

            Chunk c = new Chunk(this, device, point.X, point.Y);
            c.AddBlocks(sureThing.ToArray());

            c.status = Chunk.ChunkStatus.Initializing;

            c.Initialize();
            this.ChunkReceived(c);
        }

        private bool IsChunkLoaded(int x, int y)
        {
            for (int i = 0; i < this.loadedChunks.Count; i++)
            {
                if (this.loadedChunks[i].X == x && this.loadedChunks[i].Y == y)
                    return true;
            }

            for (int i = 0; i < this.compressedChunks.Count; i++)
            {
                if (this.compressedChunks[i].data.X == x && this.compressedChunks[i].data.Y == y)
                    return true;
            }

            return false;
        }

        public bool CastRay(Ray ray, float distance, out RayIntersectionResult? result)
        {
            float delta = 0.01f;

            BinarySortedList<ChunkPartEx2> intersectedChunkParts = new BinarySortedList<ChunkPartEx2>(100); ;

            for (int i = 0; i < this.loadedChunks.Count; i++)
            {
                for (int j = 0; j < 16; j++)
                {
                    if (this.loadedChunks[i].partsEx2[j] != null)
                    {
                        float? intersection = this.loadedChunks[i].partsEx2[j].Bounds.Intersects(ray);
                        if (intersection != null && intersection.Value < distance)
                            intersectedChunkParts.Add((double)intersection.Value, this.loadedChunks[i].partsEx2[j]);
                    }
                }
            }

            result = null;
            for (int i = 0; i < intersectedChunkParts.Count; i++)
            {
                if (this.CastRayInBoundingBox(ref ray, distance, intersectedChunkParts.list[i].Value, ref intersectedChunkParts.list[i].Value.Bounds, out result))
                    break;
                //if(CastRayInChunkPart(ray, distance, intersectedChunkParts[i], out result))
                    //break;
            }

            return (result != null);
        }
        private bool CastRayInBoundingBox(ref Ray ray, float distance, ChunkPartEx2 part, ref BoundingBox box, out RayIntersectionResult? result)
        {
            result = null;
            Vector3 childSize = (box.Max - box.Min) / 2;

            if (childSize.X < 1)
            {
                Vector3 localPosition = box.Min - new Vector3(part.Parent.X * 16, 0, part.Parent.Y * 16);
                short x = (short)((short)localPosition.X % 16);
                short y = (short)((short)localPosition.Y % 16);
                short z = (short)((short)localPosition.Z % 16);
                if (part.blocks[x, y, z] != Block.AirID)
                {
                    result = new RayIntersectionResult(part, new PointInt16(x,y ,z), Direction.PositiveX);
                }
            }
            else
            {
            
            BinarySortedList<BoundingBox> children = new BinarySortedList<BoundingBox>(8);
            for (int x = 0; x < 2; x++)
            {
                for (int y = 0; y < 2; y++)
                {
                    for (int z = 0; z < 2; z++)
                    {
                        Vector3 min = box.Min + new Vector3(x * childSize.X, y * childSize.Y, z * childSize.Z);
                        BoundingBox child = new BoundingBox(min, min + childSize);

                        float? intersection = child.Intersects(ray);
                        if (intersection.HasValue && intersection.Value < distance)
                        {
                            children.Add((double)intersection, child);
                        }
                    }
                }
            }

            for (int i = 0; i < children.Count; i++)
            {
                BoundingBox childBox = children.list[i].Value;
                if (CastRayInBoundingBox(ref ray, distance, part, ref childBox, out result))
                {
                    break;
                }
            }
            }

            return (result != null);
        }

        public void AddBlock(BlockDescriptorAdd descriptor)
        {
            int blockChunkX = (int)(descriptor.Location.X / Chunk.ChunkWidth);
            int blockChunkY = (int)(descriptor.Location.Y / 16);
            int blockChunkZ = (int)(descriptor.Location.Z / Chunk.ChunkWidth);

            Chunk chunk = this.GetChunk(blockChunkX, blockChunkZ);
            if (chunk != null)
            {
                descriptor.Location.X = (short)(descriptor.Location.X % Chunk.ChunkWidth);
                descriptor.Location.Y = (short)(descriptor.Location.Y % Chunk.ChunkWidth);
                descriptor.Location.Z = (short)(descriptor.Location.Z % Chunk.ChunkWidth);

                if (chunk.partsEx2[blockChunkY] == null)
                    chunk.InitializeChunkPart(blockChunkY);

                chunk.partsEx2[blockChunkY].BlocksPendingAdd.Enqueue(descriptor);
            }
        }

        public bool ChunkHasBeenRequested(Point p)
        {
            for (int i = 0; i < this.requestedChunks.Count; i++)
            {
                if (this.requestedChunks[i].data == p)
                    return true;
            }

            return false;
        }

        public List<OperationRequest<Chunk>> compressedChunks = new List<OperationRequest<Chunk>>();
        private ConcurrentQueue<OperationRequest<Chunk>> compressionQueue = new ConcurrentQueue<OperationRequest<Chunk>>();
        private ConcurrentQueue<OperationRequest<Chunk>> decompressionQueue = new ConcurrentQueue<OperationRequest<Chunk>>();
        private ConcurrentQueue<OperationRequest<Chunk>> disposeQueue = new ConcurrentQueue<OperationRequest<Chunk>>();
        private ConcurrentQueue<OperationRequest<Point>> requestedChunkQueue = new ConcurrentQueue<OperationRequest<Point>>();
        private ConcurrentQueue<Chunk> receivedChunkQueue = new ConcurrentQueue<Chunk>();
        private Vector3 lastPlayerPos = Vector3.Zero;
        private int qwerty = 0;
        public void Update(GameTime gameTime)
        {
            if (!this.mustNotUpdate)
            {

                Stopwatch watch = Stopwatch.StartNew();
                if ((Player.Position - lastPlayerPos).LengthSquared() > 256 && qwerty < 2)
                {
                    lastPlayerPos = Player.Position;
                    //qwerty++;

                    List<Point> chunks = this.GetPlayerChunks();

                    for (int i = 0; i < chunks.Count; i++)
                    {
                        if (!this.ChunkHasBeenRequested(chunks[i]) && !this.IsChunkLoaded(chunks[i].X, chunks[i].Y))
                        {
                            OperationRequest<Point> chunkRequest = new OperationRequest<Point>(chunks[i]);
                            this.requestedChunks.Add(chunkRequest);
                            this.requestedChunkQueue.Enqueue(chunkRequest);
                            this.updateEvent.Set();
                        }
                    }

                    for (int i = this.requestedChunks.Count - 1; i >= 0; i--)
                    {
                        if (!chunks.Contains(this.requestedChunks[i].data) && false)
                        {
                            bool cancelled = this.requestedChunks[i].SignalCancellation();
                            if (cancelled)
                            {
                                this.requestedChunks.RemoveAt(i);
                            }
                        }
                    }

                    for (int i = this.compressedChunks.Count - 1; i >= 0; i--)
                    {
                        bool active = chunks.Any(c => this.compressedChunks[i].data.X == c.X && this.compressedChunks[i].data.Y == c.Y);
                        if (active)
                        {
                            Stopwatch interlockWatch = Stopwatch.StartNew();
                            if (!this.compressedChunks[i].SignalCancellation())
                            {
                                interlockWatch.Stop();
                                Console.WriteLine("CompareExchange: {0} MS", interlockWatch.ElapsedMilliseconds);
                                if (this.compressedChunks[i].Status == OperationRequest<Chunk>.OperationStatus.Completed)
                                {
                                    this.compressedChunks[i].data.compressionStatus = Chunk.CompressionStatus.Decompressing;
                                    this.decompressionQueue.Enqueue(new OperationRequest<Chunk>(this.compressedChunks[i].data));
                                    this.compressedChunks.RemoveAt(i);
                                    this.updateEvent.Set();
                                }
                            }
                        }
                        else
                        {

                            float absoluteX = Math.Abs(this.compressedChunks[i].data.X - Player.Position.X);
                            float absoluteY = Math.Abs(this.compressedChunks[i].data.Y - Player.Position.Z);
                            if (absoluteX * absoluteX + absoluteY * absoluteY > (ChunkRadius + 1) * (ChunkRadius + 1))
                            {

                                if (this.compressedChunks[i].SignalCancellation() ||
                                    this.compressedChunks[i].Status == OperationRequest<Chunk>.OperationStatus.Completed)
                                {
                                    this.disposeQueue.Enqueue(new OperationRequest<Chunk>(this.compressedChunks[i].data));
                                    this.compressedChunks.RemoveAt(i);
                                }
                            }
                        }

                    }

                    for (int i = this.loadedChunks.Count - 1; i >= 0; i--)
                    {
                        if (this.loadedChunks[i].compressionStatus == Chunk.CompressionStatus.Decompressed)
                        {
                            bool active = chunks.Any(c => this.loadedChunks[i].X == c.X && this.loadedChunks[i].Y == c.Y);
                            if (!active)
                            {
                                OperationRequest<Chunk> compressionRequest = new OperationRequest<Chunk>(this.loadedChunks[i]);
                                this.loadedChunks[i].compressionStatus = Chunk.CompressionStatus.Compressing;
                                this.compressionQueue.Enqueue(compressionRequest);
                                this.compressedChunks.Add(compressionRequest);
                                this.loadedChunks.RemoveAt(i);
                                this.updateEvent.Set();
                            }
                        }
                    }
                }

                Chunk chunk;
                while (this.receivedChunkQueue.TryDequeue(out chunk))
                {
                    for (int i = 0; i < this.requestedChunks.Count; i++)
                    {
                        this.PopulateNeighborFields(chunk);
                        if (this.requestedChunks[i].data.X == chunk.X && this.requestedChunks[i].data.Y == chunk.Y)
                        {
                            this.requestedChunks.RemoveAt(i);
                            break;
                        }
                    }
                    //this.requestedChunks.Remove(new Point(chunk.X, chunk.Y));
                    this.loadedChunks.Add(chunk);
                }

                for (int i = 0; i < this.loadedChunks.Count; i++)
                {
                    for (int j = 0; j < 16; j++)
                    {
                        if (this.loadedChunks[i].partsEx2[j] != null)
                            this.loadedChunks[i].partsEx2[j].Update();
                    }
                }

                watch.Stop();
            }
        }

        private void PopulateNeighborFields(Chunk c)
        {
            Chunk leftNeighbor = this.GetChunk(c.X - 1, c.Y);
            Chunk rightNeighbor = this.GetChunk(c.X + 1, c.Y);
            Chunk backNeighbor = this.GetChunk(c.X, c.Y - 1);
            Chunk frontNeighbor = this.GetChunk(c.X, c.Y + 1);

            if (leftNeighbor != null)
            {
                c.LeftNeighbor = leftNeighbor;
                leftNeighbor.RightNeighbor = c;
            }

            if (rightNeighbor != null)
            {
                c.RightNeighbor = rightNeighbor;
                rightNeighbor.LeftNeighbor = c;
            }

            if (backNeighbor != null)
            {
                c.BackNeighbor = backNeighbor;
                backNeighbor.FrontNeighbor = c;
            }

            if (frontNeighbor != null)
            {
                c.FrontNeighbor = frontNeighbor;
                frontNeighbor.BackNeighbor = c;
            }
        }

        int counter = 0;

        private void ChunkReceived(Chunk c)
        {
            this.receivedChunkQueue.Enqueue(c);
        }

        private bool ChunkExistsOrIsPending(Point p)
        {
            if (this.ChunkHasBeenRequested(p))
                return true;

            for (int i = 0; i < this.loadedChunks.Count; i++)
            {
                if (this.loadedChunks[i].X == p.X && this.loadedChunks[i].Y == p.Y)
                    return true;
            }

            return false;
        }

        //private void AquireNewChunks(List<Point> newChunks)
        //{
        //    for (int i = 0; i < newChunks.Count; i++)
        //    {
        //        if (this.shutdown)
        //            break;
        //        if (!this.ChunkExistsOrIsPending(newChunks[i]))
        //        {
        //            this.requestedChunks.Add(newChunks[i]);
        //            this.RequestNewChunk(newChunks[i]);
        //        }
        //    }
        //}
    }
}
